﻿using GodSharp.Communications.Abstractions;
using GodSharp.Opc;
using GodSharp.Opc.Da;
using System;
using System.Collections.Generic;
using System.Linq;

namespace GodSharp.Communications.OpcDa
{
    /// <summary>
    /// Opc Da data changed event handler
    /// </summary>
    /// <param name="destination">The destination.</param>
    /// <param name="item">The item.</param>
    /// <param name="client">The client.</param>
    internal delegate void OpcDaCommunicationDataEventHandler(string destination, OpcTagItem item, OpcDaClient client);

    /// <summary>
    /// Opc Da data server shutdown event handler
    /// </summary>
    /// <param name="reason">The reason.</param>
    /// <param name="client">The client.</param>
    public delegate void OpcDaCommunicationShutdownEventHandler(string reason, OpcDaClient client);

#pragma warning disable CS1584 // XML comment has syntactically incorrect cref attribute
#pragma warning disable CS1658 // Warning is overriding an error
    /// <summary>
    /// OpcDaCommunicationProvider
    /// </summary>
    /// <seealso cref="GodSharp.Communications.Abstractions.CommunicationProviderBase{GodSharp.Communications.OpcDa.OpcDaCommunicationProviderOptions}" />
    /// <seealso cref="GodSharp.Communications.Abstractions.ICommunicationProvider" />
    [NameDescription("OpcDaCommunicationProvider", "Opc DataAccess protocol for GodSharp.Communications")]
#pragma warning restore CS1658 // Warning is overriding an error
#pragma warning restore CS1584 // XML comment has syntactically incorrect cref attribute
    public sealed class OpcDaCommunicationProvider : CommunicationProviderBase<OpcDaCommunicationProviderOptions, IOpcDaCommunicationHandler>, IOpcDaCommunicationProvider
    {
        Dictionary<int, OpcDaClient> clients;

        /// <summary>
        /// Gets or sets the on data.
        /// </summary>
        /// <value>
        /// The on data.
        /// </value>
        private Dictionary<string, OpcDaCommunicationDataEventHandler> OnData { get; set; }

        bool initialized = false;

        /// <summary>
        /// Initializes a new instance of the <see cref="OpcDaCommunicationProvider"/> class.
        /// </summary>
        public OpcDaCommunicationProvider()
        {
            OnData = new Dictionary<string, OpcDaCommunicationDataEventHandler>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="OpcDaCommunicationProvider"/> class.
        /// </summary>
        /// <param name="options">The options.</param>
        public OpcDaCommunicationProvider(OpcDaCommunicationProviderOptions options) : this()
        {
            Initialize(options);
        }

        /// <summary>
        /// Gets the opc client.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <returns></returns>
        public OpcDaClient GetOpcDaClient(int id)
        {
            if (!clients.ContainsKey(id)) throw new KeyNotFoundException();

            return clients[id];
        }

        /// <summary>
        /// Healthes the specified ids.
        /// </summary>
        /// <param name="ids">The ids.</param>
        /// <returns></returns>
        public IDictionary<int, CommunicationModuleStateFlags> Health(params int[] ids)
        {
            IDictionary<int, OpcDaClient> opcs = (ids.Length == 0 ? clients : clients.Where(x => ids.Contains(x.Key)).ToDictionary(x => x.Key, x => x.Value));

            if (!(opcs?.Count > 0)) return null;

            IDictionary<int, CommunicationModuleStateFlags> flags = new Dictionary<int, CommunicationModuleStateFlags>(opcs.Count);

            foreach (var item in opcs)
            {
                try
                {
                    flags.Add(item.Key, item.Value.Connected ? CommunicationModuleStateFlags.Started : CommunicationModuleStateFlags.Stopped);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }

            return flags;
        }

        /// <summary>
        /// Registers the modules.
        /// </summary>
        /// <param name="types">The types.</param>
        public void RegisterModules(Type[] types)
        {
            OnRegisterModules(types, (entry, handler) =>
            {
                if (!this.OnData.ContainsKey(entry)) this.OnData.Add(entry, handler.OnOpcDaDataHandler);
                else this.OnData[entry] += handler.OnOpcDaDataHandler;
            });
        }

        /// <summary>
        /// Restarts the specified ids.
        /// </summary>
        /// <param name="ids">The ids.</param>
        /// <returns></returns>
        public bool Restart(params int[] ids)
        {
            OpcDaClient[] opcs = (ids.Length == 0 ? clients.Values : clients.Where(x => ids.Contains(x.Key)).Select(x => x.Value))?.ToArray();

            if (!(opcs?.Length > 0)) return false;

            bool ret = Stop(ids);

            if (ret) ret = Start(ids);

            return ret;
        }

        /// <summary>
        /// Starts the specified ids.
        /// </summary>
        /// <param name="ids">The ids.</param>
        /// <returns></returns>
        public bool Start(params int[] ids)
        {
            OpcDaClient[] opcs = (ids.Length == 0 ? clients.Values : clients.Where(x => ids.Contains(x.Key)).Select(x => x.Value))?.ToArray();

            if (!(opcs?.Length > 0)) return false;

            StateFlag = CommunicationModuleStateFlags.Starting;

            bool[] state = new bool[opcs.Length];
            int index = 0;

            foreach (var item in opcs)
            {
                try
                {
                    item.Connent();

                    state[index++] = item.Connected;
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }

            bool _AllTrue = state.All(x => x);

            bool _AllFalse = state.All(x => !x);

            StateFlag = _AllTrue ? CommunicationModuleStateFlags.Started : (_AllFalse ? CommunicationModuleStateFlags.Stopped : CommunicationModuleStateFlags.StartWithError);
            
            return StateFlag == CommunicationModuleStateFlags.Started;
        }

        /// <summary>
        /// Stops the specified ids.
        /// </summary>
        /// <param name="ids">The ids.</param>
        /// <returns></returns>
        public bool Stop(params int[] ids)
        {
            OpcDaClient[] opcs = (ids.Length == 0 ? clients.Values : clients.Where(x => ids.Contains(x.Key)).Select(x => x.Value))?.ToArray();

            if (!(opcs?.Length > 0)) return false;

            StateFlag = CommunicationModuleStateFlags.Stopping;
            bool[] state = new bool[opcs.Length];
            int index = 0;

            foreach (var item in opcs)
            {
                try
                {
                    item.Disconnect();

                    state[index++] = item.Connected;
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }

            bool _AllTrue = state.All(x => x);

            bool _AllFalse = state.All(x => !x);

            StateFlag = _AllFalse ? CommunicationModuleStateFlags.Stopped : (_AllTrue ? CommunicationModuleStateFlags.Started : CommunicationModuleStateFlags.StopWithError);
            
            return StateFlag == CommunicationModuleStateFlags.Stopped;
        }

        /// <summary>
        /// Called when [initialize].
        /// </summary>
        /// <param name="options">The options.</param>
        public override void OnInitialize(OpcDaCommunicationProviderOptions options)
        {
            base.OnInitialize(options);
        }

        private bool Initialize(OpcDaCommunicationProviderOptions options)
        {
            if (initialized) throw new InvalidOperationException("The provider is initialized");
            
            clients = options.Clients.Select(x => new
            {
                Id = x.Id,
                Opc = new OpcDaClient(o =>
                {
                    o.ProgId = x.Server.ProgId;
                    o.ProgHost = x.Server.Host;
                    o.Tags = x.Tags.Select(t => new OpcTagItem()
                    {
                        Id = t.Id,
                        ItemId = t.ItemId,
                        GroupName = t.GroupName,
                        Misc = t.EntryPoint
                    }).ToArray();
                    o.DefaultGroupUpdateRate = x.Server.DefaultUpdateRate;
                })
                { DataChange = OpcDataChangedHandler, Shutdown = OpcShutdownEventHandler }
            }).ToDictionary(x => x.Id, x => x.Opc);

            initialized = true;

            StateFlag = CommunicationModuleStateFlags.Initialized;

            return initialized;
        }

        /// <summary>
        /// Opcs the data changed handler.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        /// <param name="list">The list.</param>
        private void OpcDataChangedHandler(DataChangeParameter parameter, IEnumerable<OpcTagItem> list)
        {
            if (list == null) return;
            string ep = null;

            foreach (OpcTagItem item in list)
            {
                if (item.Value == null) continue;
                ep = item.Misc as string;

                if (ep.IsNullOrWhiteSpace() || this.OnData?.ContainsKey(ep) == false) continue;

                this.OnData[ep]?.Invoke(ep, item, parameter.Client);
            }
        }

        /// <summary>
        /// Opcs the shutdown event handler.
        /// </summary>
        /// <param name="reason">The reason.</param>
        /// <param name="client">The client.</param>
        private void OpcShutdownEventHandler(string reason, OpcDaClient client)
        {
            options?.ShutdownEventHandler?.Invoke(reason, client);
        }
    }
}